package com.zimo.download.thread;

import com.zimo.download.Download;
import com.zimo.download.DownloadCacheImpl;
import com.zimo.download.DownloadImpl;
import com.zimo.download.exception.DownloadException;
import com.zimo.download.info.PointerOffset;
import com.zimo.download.info.ServerFileINFO;
import com.zimo.utils.Connection;
import org.apache.log4j.Logger;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 多线程下载管理器，只分配分配线程下载任务，并开启下载
 * 下载使用方法，
 * 1. 利用有参构造来初始化管理器
 * 2. 调用startDownload方法来进行下载
 * 方法2
 * 1. 利用无参构造，和set方法来初始化管理器
 * 2. 调用statDownload方法来进行下载
 * 方法2
 * 1. 调用continuation方法来进行下载，但是要传参一个缓存文件的路径
 *
 * 暂停下载方法
 * 1. 无论哪个实例都能暂停任意一个下载
 * 2. 调用stopDownload方法传入被暂停的文件名来暂停，文件名一定到传对。如果你没有指定文件名的话，那么通过ServerFileINFO.getFileName()来获取，如果你自定义了文件名，那么只能靠你自己了
 */
public class ThreadManageDownload implements Download  {

    private URL url = null; //url地址
    private int threadCount = 32;
    private String filePath = "";

    private String fileName = "";
    private Download download= new DownloadImpl(); //下载器。多线程的下载器对象是同一个，所以这些同一个下载器之间的全局变量是所以线程共享的
    //获取logger对象： 传入当前类的class，这样代表这个类输出的日志信息
    private Logger logger = Logger.getLogger(this.getClass());

    /**
     *
     * @param url   文件链接
     * @param filePath  文件保存地址
     * @param threadCount   线程数
     * @param downloadImpl  下载器
     * @throws IOException
     */
    public ThreadManageDownload(URL url, String filePath, int threadCount, Download downloadImpl) throws IOException {
        this.filePath=filePath;
        this.fileName = new ServerFileINFO().getFileName(url);
        this.url=url;
        this.threadCount=threadCount;
        this.download=downloadImpl;
        this.download=downloadImpl;

    }

    public ThreadManageDownload() {
    }

    @Deprecated
    @Override
    public boolean startDownload(URL url, String filePath, PointerOffset po) throws IOException {
        long fileSize = ServerFileINFO.getFileSize(url);//获取文件大小
        //输出日志
        log(true,fileSize);
        //获取不到文件大小不进行多线程下载
        if(fileSize == -1)   return false;
        setThreadCount(32);
        //开始多线程下载文件
        Set<PointerOffset> pointerOffsets = CalculationBytes(threadCount, url);//获取一个文件的所有的线程下载指针的偏移量
        //线程根据指针偏移量来下载文件
        int i = 0;
        for (PointerOffset pointerOffset : pointerOffsets) {
            //设置好，下载地址，文件保存地址，线程指针偏移量，下载实现类
            //这里将保持路径和文件名分离了，在底层他们是不分离的
            Thread thread = new Thread(new ThreadDownload(url, filePath+fileName, pointerOffset,download));
            thread.setName(fileName+" --- "+i);//设置线程名字
            thread.start();//开始下载
            i++;
        }
        return true;
    }

    /**
     * 开始下载
     * @param b     不管服务器是否支持都进行多线程分段下载
     * @return
     * @throws IOException
     */
    public boolean startDownload(boolean b) throws IOException, DownloadException {
        //-------------------------------------------下载前检查------------------------------------------------------
            //判断下载参数是否全部配置正确
            //判断文件名是否有
            if (fileName.equals("")){
                fileName = ServerFileINFO.getFileName(url);
            }
            //判断url是否存在
            if (url.equals("")){
                throw new DownloadException("URL为空");
            }
            //判断保存地址是否正确
            if (true){
                File file = new File(filePath);
                boolean directory = file.isDirectory();
                boolean mkdirs = file.mkdirs();
                //如果不能创建文件夹就抛出这个异常
                if ((filePath.equals("//") || filePath.equals("") ||( directory==false && mkdirs ==false))){
                    throw new DownloadException("文件保存地址异常："+filePath);
                }
            }
        //-------------------------------------------下载前检查------------------------------------------------------

            //获取文件大小
            long fileSize = ServerFileINFO.getFileSize(url);
            fileSizeToDownload=fileSize;//需要下载的文件大小
            //获取不到文件大小不进行多线程下载
            if(fileSize < 0)   return false;
            log(b,fileSize);
            //判断服务器是否支持多线程下载
            //b 是进行强制下载
            if (Connection.isSupportThread(url) || b) {
                //开始多线程下载文件
                Set<PointerOffset> pointerOffsets = CalculationBytes(threadCount, url);//获取一个文件的所有的线程下载指针的偏移量
                //线程根据指针偏移量来下载文件
                int i = 0;
                for (PointerOffset pointerOffset : pointerOffsets) {
                    //设置好，下载地址，文件保存地址，线程指针偏移量，下载实现类
                    //这里将保持路径和文件名分离了，在底层他们是不分离的
                    Thread thread = new Thread(new ThreadDownload(url, filePath + fileName, pointerOffset, download));
                    thread.setName(fileName + " --- " + i);//设置线程名字
                    thread.start();//开始下载
                    i++;
                }
            } else {
                return false;
            }
            return true;
    }

    /**
     * 开始下载
     * @return
     * @throws IOException
     */
    public boolean startDownload() throws IOException, DownloadException {
       return startDownload(false);
    }

    /**
     * 续传
     * @param timFilePath  缓存文件路径
     * @return  是否开始下载
     */
    public  boolean continuation(String timFilePath) throws IOException {
        //获取断点类
        DownloadCacheImpl downloadCache = new DownloadCacheImpl(timFilePath);
        //获取断点文件里面的指针集合
        List<PointerOffset> pointerOffsets = downloadCache.getPointerOffsets();

        //从缓存文件里面获取URL
        String url = downloadCache.readURL();
        //从缓存文件里面获取文件保存位置
        String filepath = downloadCache.readPath();

        logger.info("多线程下载管理器已创建" + "\n>>>URL: "+url+ "\n>>>文件名称: "+ServerFileINFO.getFileName(new URL(url))+ "\n>>>文件大小: "+ServerFileINFO.getFileSize(new URL(url))+" byte"+ "\n>>>文件保存地址: "+filepath+ "\n>>>线程数： "+pointerOffsets.size()+ "\n>>>下载器: "+download+ "\n>>>是否强制分块下载: "+true+"\n>>>当前时间: "+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));

        //开始多线程下载文件
        //线程根据指针偏移量来下载文件
        int i = 0;
        long startSum = 0;//开始位置大小总和
        long endSum = 0;//结束位置大小总和
        for (PointerOffset pointerOffset : pointerOffsets) {
            if(pointerOffset.getEND() >= 0){
                startSum+=pointerOffset.getSTART();
                endSum+=pointerOffset.getEND();
                //设置好，下载地址，文件保存地址，线程指针偏移量，下载实现类
                //这里将保持路径和文件名分离了，在底层他们是不分离的
                Thread thread = new Thread(new ThreadDownload(new URL(url), filepath, pointerOffset,new DownloadImpl()));
                thread.setName(ServerFileINFO.getFileName(new URL(url)) +" --- "+i);//设置线程名字
                thread.start();//开始下载
                i++;
            }else {
                logger.error("断点读取到异常信息 ：\nend="+pointerOffset.getEND()+"\n断点个数: "+ pointerOffsets.size()+"\nurl: "+url+"\n文件地址: "+filepath+"\n缓存文件地址： "+timFilePath+"\n>>>当前时间: "+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            }
        }
        //计算需要下载的文件大小
        fileSizeToDownload= endSum-startSum;
        return true;
    }

    //日志
    private void log(boolean b,long fileSize) throws IOException {
        logger.info("多线程下载管理器已创建" + "\n>>>URL: "+url+ "\n>>>文件名称: "+fileName+ "\n>>>文件大小: "+fileSize+ "\n>>>文件保存地址: "+filePath+ "\n>>>线程数： "+threadCount+ "\n>>>下载器: "+download+ "\n>>>是否支持多线程下载: "+ Connection.isSupportThread(url)+ "\n>>>是否强制分块下载: "+b+"\n>>>当前时间: "+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    }

    /**
     * 暂停线程
     */
    @Override
    public boolean stopDownload(String name) {
        return download.stopDownload(name);
    }




    /**
     * 为每个线程平均分配字节
     * @return List<StartEndPosition>
     *
     */
    private Set<PointerOffset> CalculationBytes(int threadCount, URL url) throws IOException {
        synchronized (this.getClass()) {
            Set<PointerOffset> Index  =  new HashSet<PointerOffset>();//文件段指针集合
            long FileSize= new ServerFileINFO().getFileSize(url);//文件大小

            //每个线程分配多少下载量的平均值
            long blockSize = FileSize /threadCount;
            //计算每个线程下载的开始位置和结束位置
            for (int i = 0; i < threadCount; i++) {
                long startIndex = i * blockSize;   //每个线程下载的开始位置

                //i不能为零否则会分配失败所以写成 i+1。
                // blockSize - 1要将1个字节抛给后面的线程否则后面线程会重复下载同一个位置的字节,这样会在同一个位置写入两次但是不会丢失字节

                long endIndex = (i+1)*blockSize - 1;//计算终止位置。

                //判断是否为最后一个线程
                //最后一个线程必须包含文件剩余的字节
                if (i == threadCount - 1) {
                    //讲该线程的起始位置到文件末尾的字节全部归他
                    endIndex = FileSize;
                }

                Index.add( new PointerOffset(startIndex, endIndex));//添加
            }
            return Index;//返回分配好的集合
        }

    }

    /**
     * 如果不想指定就请同 ServerFileINFO.getFileName(url)来获取
     * @param fileName
     */
    public void setFileName(String fileName) {
        this.fileName = fileName;
    }


    public void setUrl(URL url) {
        this.url = url;
    }

    public void setThreadCount(int threadCount) {
        this.threadCount = threadCount;
    }

    public void setFilePath(String filePath) {
        this.filePath = filePath+"//";
    }


    public void setDownload(Download download) {
        this.download = download;
    }

    public URL getUrl() {
        return url;
    }

    public int getThreadCount() {
        return threadCount;
    }

    public Download getDownload() {
        return download;
    }

    public String getFileName() {
        return fileName;
    }

    @Override
    public int getFinishThread() {
        return download.getFinishThread();
    }

    @Override
    public long getDownloadingFileSize() {
        return download.getDownloadingFileSize();
    }

    /**
     * 需要下载的文件大小
     * @return
     */
    private long fileSizeToDownload = 0;
    /**
     * 需要下载的文件大小
     * @return
     */
    public long getFileSize() {
        return fileSizeToDownload;
    }
    public String getFilePath() {
        return filePath;
    }


}
